package apiroute

import (
	"fmt"
	"git.sr.ht/~mil/mobroute/src/db"
	"git.sr.ht/~mil/mobroute/src/logger"
	"git.sr.ht/~mil/mobroute/src/utilfuncs"
)

func formatRouteLegs(
	connectionsVerbose *[]db.ConnectionVerbose,
	transfers *[]db.Transfer,
	walkSpeedKmHr float64,
	from utilfuncs.Coord,
	to utilfuncs.Coord,
	debugFormatting bool,
) *[]RouteLeg {
	var (
		connectionOrigin = (*connectionsVerbose)[0]
		connectionDest   = (*connectionsVerbose)[len(*connectionsVerbose)-1]

		walkDistKmOrigin = utilfuncs.DistHaversine(
			from,
			utilfuncs.Coord{Lat: connectionOrigin.FromStopLat, Lon: connectionOrigin.FromStopLon},
		)
		walkDistKmDest = utilfuncs.DistHaversine(
			to,
			utilfuncs.Coord{Lat: connectionDest.ToStopLat, Lon: connectionDest.ToStopLon},
		)
		walkFromLabel = "Origin"
		walkToLabel   = "Destination"

		walkFromS = walkCalcSecs(walkDistKmOrigin, walkSpeedKmHr)
		walkToS   = walkCalcSecs(walkDistKmDest, walkSpeedKmHr)

		legWalkOrigin = RouteLeg{
			LegType:       "walk",
			LegDuration:   utilfuncs.DurationFormatSecs(walkFromS),
			LegFromCoords: fmt.Sprintf("%0.04f, %0.04f", from.Lat, from.Lon),
			LegToCoords:   fmt.Sprintf("%0.04f, %0.04f", connectionOrigin.FromStopLat, connectionOrigin.FromStopLon),
			LegDurationS:  walkFromS,
			LegBeginTimeS: connectionOrigin.FromStopTime - walkFromS,
			LegEndTimeS:   connectionOrigin.FromStopTime,
			LegBeginTime:  utilfuncs.TimeHMSFormatSecs(connectionOrigin.FromStopTime - walkFromS),
			LegEndTime:    utilfuncs.TimeHMSFormatSecs(connectionOrigin.FromStopTime),

			WalkFrom:   &walkFromLabel,
			WalkTo:     &connectionOrigin.FromStopName,
			WalkDistKm: &walkDistKmOrigin,
		}
		legWalkDest = RouteLeg{
			LegType:       "walk",
			LegDuration:   utilfuncs.DurationFormatSecs(walkToS),
			LegDurationS:  walkToS,
			LegFromCoords: fmt.Sprintf("%0.04f, %0.04f", connectionDest.ToStopLat, connectionDest.ToStopLon),
			LegToCoords:   fmt.Sprintf("%0.04f, %0.04f", to.Lat, to.Lon),
			LegBeginTimeS: connectionDest.ToStopTime,
			LegEndTimeS:   connectionDest.ToStopTime + walkToS,
			LegBeginTime:  utilfuncs.TimeHMSFormatSecs(connectionDest.ToStopTime),
			LegEndTime:    utilfuncs.TimeHMSFormatSecs(connectionDest.ToStopTime + walkToS),

			WalkFrom:   &connectionDest.ToStopName,
			WalkTo:     &walkToLabel,
			WalkDistKm: &walkDistKmDest,
		}
	)

	legs := []RouteLeg{legWalkOrigin}
	legs = append(legs, *formatRouteLegsConnections(connectionsVerbose, transfers, debugFormatting)...)
	legs = append(legs, legWalkDest)
	return &legs
}

func formatRouteLegsConnections(
	connectionsVerbose *[]db.ConnectionVerbose,
	transfers *[]db.Transfer,
	debugFormatting bool,
) *[]RouteLeg {
	var (
		legsGroupedByTripID = map[string][]*db.ConnectionVerbose{}
		returnLegs          = []RouteLeg{}
	)

	for _, c := range *connectionsVerbose {
		if _, exists := legsGroupedByTripID[c.TripID]; !exists {
			legsGroupedByTripID[c.TripID] = []*db.ConnectionVerbose{}
		}
		cc := c
		legsGroupedByTripID[c.TripID] = append(legsGroupedByTripID[c.TripID], &cc)
	}

	for _, c := range *connectionsVerbose {
		if legConnections, exists := legsGroupedByTripID[c.TripID]; !exists {
			continue
		} else {
			var (
				connectionFrom          = legConnections[0]
				connectionTo            = legConnections[len(legConnections)-1]
				connectionPrettTimeFrom = utilfuncs.TimeHMSFormatSecs(connectionFrom.FromStopTime)
				connectionPrettyTimeTo  = utilfuncs.TimeHMSFormatSecs(connectionTo.ToStopTime)
				durationSeconds         = connectionTo.ToStopTime - connectionFrom.FromStopTime
				stopsDetails            = func() []*StopDetails {
					m := []*StopDetails{}
					for j, c := range legConnections {
						m = append(m, &StopDetails{
							Time:   utilfuncs.TimeHMSFormatSecs(c.FromStopTime),
							Name:   c.FromStopName,
							Coords: fmt.Sprintf("%0.04f, %0.04f", c.FromStopLat, c.FromStopLon),
						})
						if j == len(legConnections)-1 {
							m = append(m, &StopDetails{
								Time:   utilfuncs.TimeHMSFormatSecs(c.ToStopTime),
								Name:   c.ToStopName,
								Coords: fmt.Sprintf("%0.04f, %0.04f", c.ToStopLat, c.ToStopLon),
							})
						}
					}
					return m
				}()
				appendLeg = RouteLeg{
					LegType:       "trip",
					LegDuration:   utilfuncs.DurationFormatSecs(durationSeconds),
					LegDurationS:  durationSeconds,
					LegFromCoords: fmt.Sprintf("%0.04f, %0.04f", connectionFrom.FromStopLat, connectionFrom.FromStopLon),
					LegToCoords:   fmt.Sprintf("%0.04f, %0.04f", connectionTo.ToStopLat, connectionTo.ToStopLon),
					LegBeginTime:  connectionPrettTimeFrom,
					LegBeginTimeS: connectionFrom.FromStopTime,
					LegEndTime:    connectionPrettyTimeTo,
					LegEndTimeS:   connectionTo.ToStopTime,

					TripRouteHeadsign: connectionFrom.TripHeadsign,
					TripRoute:         coalesce(connectionFrom.RouteShortName, connectionFrom.RouteLongName),
					TripFrom:          &connectionFrom.FromStopName,
					TripFromSIDU:      &connectionFrom.FromStopSIDU,
					TripTo:            &connectionTo.ToStopName,
					TripToSIDU:        &connectionTo.ToStopSIDU,
					TripSource:        &connectionTo.Source,
					TripStops:         &stopsDetails,
				}
			)

			if len(returnLegs) > 0 {
				var (
					legTransferFrom = returnLegs[len(returnLegs)-1]
					legTransferTo   = appendLeg
					transfer        *db.Transfer
				)
				for _, t := range *transfers {
					a := t.FromSSIDU == *legTransferFrom.TripToSIDU && t.ToSSIDU == *legTransferTo.TripFromSIDU
					b := t.ToSSIDU == *legTransferFrom.TripToSIDU && t.FromSSIDU == *legTransferTo.TripFromSIDU
					if a || b {
						tt := t
						transfer = &tt
						break
					}
				}
				if transfer == nil {
					logger.Fatal("Should be impossible - failed to lookup transfer in formatter?")
				}

				var (
					transferDurationTotal  = legTransferTo.LegBeginTimeS - legTransferFrom.LegEndTimeS
					transferDurationActive = transfer.TransferTime
					transferDurationDwell  = transferDurationTotal - transferDurationActive
				)

				returnLegs = append(returnLegs, RouteLeg{
					LegType:       "transfer",
					LegDuration:   utilfuncs.DurationFormatSecs(transferDurationTotal),
					LegDurationS:  transferDurationTotal,
					LegFromCoords: legTransferFrom.LegToCoords,
					LegToCoords:   legTransferTo.LegFromCoords,
					LegBeginTime:  utilfuncs.TimeHMSFormatSecs(legTransferFrom.LegEndTimeS),
					LegBeginTimeS: legTransferFrom.LegEndTimeS,
					LegEndTime:    utilfuncs.TimeHMSFormatSecs(legTransferTo.LegBeginTimeS),
					LegEndTimeS:   legTransferTo.LegBeginTimeS,

					TransferFrom:     legTransferFrom.TripTo,
					TransferTo:       appendLeg.TripFrom,
					TransferDdwell:   ptr(utilfuncs.DurationFormatSecs(transferDurationDwell)),
					TransferDactive:  ptr(utilfuncs.DurationFormatSecs(transferDurationActive)),
					TransferDistKM:   transfer.DistKM,
					TransferSources:  ptr([]int{transfer.Source, transfer.SourcePair}),
					TransferCategory: ptr(transfer.TransferCategory),
				})
			}
			returnLegs = append(returnLegs, appendLeg)
			if debugFormatting {
				returnLegs[len(returnLegs)-1].TripConnections = &legConnections
				returnLegs[len(returnLegs)-1].TripID = &connectionFrom.TripID
			}
			delete(legsGroupedByTripID, c.TripID)
		}
	}
	return &returnLegs
}
